package bavaria.hightech.banking;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import bavaria.hightech.comparator.*;
import bavaria.hightech.exception.WrongValueException;
import bavaria.hightech.statement.IStatementFormatter;
import bavaria.hightech.statement.StatementFormatterFactory;

/**
 * Basisclass of a bank account
 * 
 * @author Christian Adler, Daniel Fuss
 */
public abstract class Account {

	
	private final int accountNumber;
	private Money accountBalance;
	private String depositor;
	private final Calendar starttime;
	private List<Transaction> list;
	private Map<String, Comparator<Transaction>> comparatorMap;
	
	/**
	 * Constructor of an account.
	 * @param accountNumber Auto-generated accountNumber
	 * @param depositor Depositor of the account
	 * @param starttime Date and Time when the account was opened
	 */
	public Account(int accountNumber, String depositor, Calendar starttime){
		this.accountNumber = accountNumber;
		this.depositor = depositor;
		this.starttime = starttime;
		accountBalance = new Money(0, ECurrency.EUR);
		list = new ArrayList<Transaction>();
		comparatorMap = new Hashtable<String, Comparator<Transaction>>();
		comparatorMap.put("Date", new DateComp());
		comparatorMap.put("Reason", new ReasonComp());
		comparatorMap.put("Value", new ValueComp());
	}
	
	/**
	 * Method for cash-in to this account
	 * @param money Money-Object (Value and Currency) which should be banked
	 * @param reason Reson for the cash-in
	 * @param time Date and Time of the cash-in
	 * @return Success
	 * @throws WrongValueException
	 */
	public boolean cashIn(Money money, String reason, Date time) throws WrongValueException{
		if(money.getValue() >= 0){
			return update(money, reason, true, time); 
		} else {
			throw new WrongValueException("negativer Betrag");
		}	
	}

	/**
	 * Method for cash-out to this account
	 * @param money Money-Object (Value and Currency) which should be disbursed
	 * @param reason Reason for the cash-out
	 * @param time Date and Time of the cash-out
	 * @return Success
	 * @throws WrongValueException
	 */
	public boolean cashOut(Money money, String reason, Date time) throws WrongValueException{
		if(money.getValue() > 0){
			return update(money, reason, false, time);
		} else {
			throw new WrongValueException("negativer Betrag");
		}	
	}
	
	public int getAccountNumber(){
		return accountNumber;
	}
	
	public long getAccountBalance(){
		return accountBalance.getValue();
	}
	
	/**
	 * @return currency (as an enum) of the account balance
	 */
	public ECurrency getCurrency(){
		return accountBalance.getCurrency();
	}
	
	public String getDepositor(){
		return depositor;
	}

	/*
	 * Gateway between method of transaction (cash-in, cash-out) and account balance
	 * @param money Money-Object (Value and Currency) which should be added
	 * @param reason Reason for the addition
	 * @param sign true for a positive money-value, false for a negative money-value
	 * @param time Date and Time of the update
	 * @return Success
	 */
	private boolean update(Money money, String reason, boolean sign, Date time){
		if (sign)
			accountBalance.add(money);
		else
			accountBalance.sub(money);
		return list.add(new Transaction(reason, money, time));
	}
	
	public String statement(String mimeType, String sortArgument, Locale bankLocale){
		Collections.sort(list, comparatorMap.get(sortArgument));
		StatementFormatterFactory formatterFactory = new StatementFormatterFactory(bankLocale);
		IStatementFormatter formatter = formatterFactory.getStatementFormatter(mimeType);
		formatter.addAccountData(this);
		for (Iterator<Transaction> iterator = list.iterator(); iterator.hasNext();)
			formatter.addTransaction(iterator.next());
//		formatter.addAccountBalance(accountBalance.getValue());
		return formatter.getFormattedText();
	}
	
	protected abstract void payInterest() throws WrongValueException;

}